Skip to content

wallet: SQL-only signer account-secret fallback#1254

Open
yyforyongyu wants to merge 3 commits into
sql-walletfrom
impl-signer-account-secret
Open

wallet: SQL-only signer account-secret fallback#1254
yyforyongyu wants to merge 3 commits into
sql-walletfrom
impl-signer-account-secret

Conversation

@yyforyongyu

Copy link
Copy Markdown
Collaborator

Supersedes #1249, which auto-closed when its base branch impl-wallet-metadata-store was deleted; rebased onto sql-wallet. Adds the account-secret lookup contract, exposes store account secrets through the cache, and derives SQL-only account private keys via the keyVault in the signer.

@coveralls

coveralls commented Jun 3, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 27161315132

Warning

No base build found for commit 6c09fa3 on sql-wallet.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 54.492%

Details

  • Patch coverage: 124 uncovered changes across 9 files (253 of 377 lines covered, 67.11%).

Uncovered Changes

File Changed Covered %
wallet/signer.go 141 102 72.34%
wallet/internal/sql/pg/sqlc/accounts.sql.go 21 0 0.0%
wallet/internal/db/pg/accountstore_getaccountsecret.go 76 58 76.32%
wallet/internal/db/sqlite/accountstore_getaccountsecret.go 76 58 76.32%
wallet/internal/sql/pg/sqlc/db.go 8 0 0.0%
wallet/internal/db/kvdb/accountstore_getaccountsecret.go 7 0 0.0%
wallet/internal/sql/sqlite/sqlc/db.go 8 1 12.5%
wallet/internal/db/accountstore_getaccountsecret.go 8 4 50.0%
wallet/internal/bwtest/mock/store.go 7 5 71.43%
Total (11 files) 377 253 67.11%

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 53120
Covered Lines: 28946
Line Coverage: 54.49%
Coverage Strength: 15887.77 hits per line

💛 - Coveralls

@yyforyongyu yyforyongyu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (deepseek-v4-pro)

@yyforyongyu

Copy link
Copy Markdown
Collaborator Author

LGTM 🔑
(claude-opus-4-8[1m])

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enables wallet signing operations to derive private keys for SQL-only accounts (accounts present in the SQL store but not mirrored in legacy waddrmgr) by introducing an account-secret lookup contract on db.AccountStore and wiring a keyVault-based fallback into the signer.

Changes:

  • Add AccountStore.GetAccountSecret (+ pg/sqlite implementations + sqlc queries) to fetch encrypted account-level private key material (or detect watch-only / not-found).
  • Update wallet/signer key-resolution to fall back from legacy waddrmgr derivation to store-backed account secrets for SQL-only accounts.
  • Add unit/integration tests covering SQL-only signing paths and store contract behavior.

Reviewed changes

Copilot reviewed 13 out of 19 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
wallet/signer.go Adds store-backed key derivation fallback for SQL-only accounts and threads context.Context through signer key-resolution.
wallet/signer_test.go Adds coverage for SQL-only account signing (unlocking script, DerivePrivKey, SignDigest, ECDH, ComputeRawSig) and error cases.
wallet/cache.go Extends runtime cache seam to expose GetAccountSecret (currently pass-through).
wallet/internal/db/interface.go Extends AccountStore interface with GetAccountSecret contract.
wallet/internal/db/data_types.go Introduces AccountSecret + GetAccountSecretQuery data types.
wallet/internal/db/accounts_common.go Adds GetAccountSecretQuery.Validate and backend-independent error for unsupported secrets.
wallet/internal/db/sqlite/accounts.go Implements GetAccountSecret for SQLite store + row mapping and error mapping.
wallet/internal/db/pg/accounts.go Implements GetAccountSecret for PostgreSQL store + row mapping and error mapping.
wallet/internal/db/kvdb/accountstore.go Implements GetAccountSecret as unsupported (ErrAccountSecretUnavailable) for kvdb backend.
wallet/internal/db/itest/account_store_test.go Adds itest verifying GetAccountSecret distinguishes spendable/watch-only/not-found outcomes.
wallet/internal/bwtest/mock/store.go Updates store mock to satisfy the extended AccountStore interface.
wallet/internal/sql/sqlite/queries/accounts.sql Adds GetAccountSecret SQL query for sqlite sqlc generation.
wallet/internal/sql/sqlite/sqlc/accounts.sql.go Generated sqlite sqlc code for GetAccountSecret.
wallet/internal/sql/sqlite/sqlc/querier.go Adds GetAccountSecret to sqlite sqlc Querier interface.
wallet/internal/sql/sqlite/sqlc/db.go Prepares/closes the sqlite GetAccountSecret statement.
wallet/internal/sql/pg/queries/accounts.sql Adds GetAccountSecret SQL query for pg sqlc generation.
wallet/internal/sql/pg/sqlc/accounts.sql.go Generated pg sqlc code for GetAccountSecret.
wallet/internal/sql/pg/sqlc/querier.go Adds GetAccountSecret to pg sqlc Querier interface.
wallet/internal/sql/pg/sqlc/db.go Prepares/closes the pg GetAccountSecret statement.
Files not reviewed (6)
  • wallet/internal/sql/pg/sqlc/accounts.sql.go: Language not supported
  • wallet/internal/sql/pg/sqlc/db.go: Language not supported
  • wallet/internal/sql/pg/sqlc/querier.go: Language not supported
  • wallet/internal/sql/sqlite/sqlc/accounts.sql.go: Language not supported
  • wallet/internal/sql/sqlite/sqlc/db.go: Language not supported
  • wallet/internal/sql/sqlite/sqlc/querier.go: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread wallet/signer.go Outdated
Comment thread wallet/signer.go Outdated
@yyforyongyu

Copy link
Copy Markdown
Collaborator Author

LGTM 🚀 (gemini-3.5-flash)

@yyforyongyu

Copy link
Copy Markdown
Collaborator Author

LGTM 🔐 (gpt-5.5)

@saubyk saubyk added this to lnd v0.22 Jun 3, 2026
@github-project-automation github-project-automation Bot moved this to Backlog in lnd v0.22 Jun 3, 2026
@yyforyongyu yyforyongyu force-pushed the impl-signer-account-secret branch 2 times, most recently from 59c3541 to a1832b2 Compare June 4, 2026 07:08

@yyforyongyu yyforyongyu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (deepseek-v4-pro)

@yyforyongyu yyforyongyu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (deepseek-v4-pro)

@yyforyongyu

Copy link
Copy Markdown
Collaborator Author

LGTM 🔑
(claude-opus-4-8[1m])

@yyforyongyu

Copy link
Copy Markdown
Collaborator Author

LGTM 🐿️ (gpt-5.5)

@yyforyongyu yyforyongyu force-pushed the impl-signer-account-secret branch from a1832b2 to 9206c0c Compare June 5, 2026 12:13
@yyforyongyu

Copy link
Copy Markdown
Collaborator Author

LGTM 🔑
(claude-opus-4-8[1m])

// one account. The returned EncryptedPrivateKey is still ciphertext;
// callers must decrypt it through the wallet key vault before deriving
// private child keys.
GetAccountSecret(ctx context.Context, query GetAccountSecretQuery) (

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i am questioning why do we need this method - when the wallet is unlocked, we will cache the secrets in the cache? thus that whenever we need to create secrets, we will use the cache to do so?
in addition what is the relationship among account secret, wallet secret, keyvault and cache?

@yyforyongyu

Copy link
Copy Markdown
Collaborator Author

On the account-secret / cache / keyvault question:

I think the durable encrypted account secret is still needed for SQL-only accounts, but the long-term domain boundary should be keyvault rather than signer or general wallet code fetching AccountSecret directly.

Current state in this branch:

  • AccountSecret is the encrypted account-level xpriv persisted for SQL accounts.
  • keyVault decrypts that ciphertext and is also used when creating/encrypting account material.
  • runtimeCache is only a pass-through seam today; w.cache.GetAccountSecret delegates to store.GetAccountSecret.
  • signer falls back to this path only after the legacy waddrmgr scope/account/cache path misses.

Ideal shape:

  • db.Store persists encrypted secret blobs only.
  • keyvault owns unlock state, durable secret reads, decrypting, caching, derivation, and zeroing.
  • signer asks keyvault for a typed derived private key, not for AccountSecret ciphertext.

So I agree this should not become the permanent public AccountStore-facing signing API. For this PR, either we keep GetAccountSecret as transitional persistence plumbing and document that, or we move the read behind a keyvault-facing internal interface now and have signer depend only on keyvault derivation.

Introduce GetAccountSecret on db.AccountStore so the wallet
signer can read the encrypted account-level signing material
that SQL backends persist for SQL-only accounts. The kvdb
backend returns db.ErrAccountSecretUnavailable because its
signing path still resolves through the legacy waddrmgr
address manager.

Add the matching pg/sqlite sqlc queries, account_secrets
itest coverage for both SQL backends, and a GetAccountSecret
method on the shared walletmock.Store so wallet-side tests can
stub the new contract.
Add GetAccountSecret to runtimeCache so wallet-side consumers
can read SQL-backed account signing material through a single
boundary. The storeRuntimeCache implementation is a pass-through
today, marked with the same TODO(yy) as the other cache reads
so the cache layer can grow typed error wrapping later without
churning callers.
When waddrmgr reports an account or scope miss, the legacy
DeriveFromKeyPathCache fallback also fails for accounts that
only live in the SQL store. Fall back to the encrypted
account-level private key fetched from the store cache,
decrypt it through keyVault, derive the requested branch and
index, and zero intermediate key material before returning.

Add SQLite-backed signing coverage for a SQL-only derived
account in signer_test so the fallback exercises a real
SQL store end-to-end and the existing kvdb-backed coverage
keeps regressing the legacy path.
@yyforyongyu yyforyongyu force-pushed the impl-signer-account-secret branch from 9206c0c to b0f6271 Compare June 8, 2026 19:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants